package pers.sen.health.security;


import com.alibaba.fastjson.JSONObject;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import pers.sen.health.entity.Result;
import pers.sen.health.jwt.JwtUtils;
import pers.sen.health.jwt.UserInfo;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 对前端携带的token做解析（在认证过滤器前执行）
 * 1. 没有token（未登录，直接放行，做下面的认证校验）
 * 2. 解析token
 */
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    private JWTProperties jwtProperties;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, JWTProperties jwtProperties) {
        super(authenticationManager);
        this.jwtProperties = jwtProperties;
    }

    /**
     * 过滤操作
     *
     * @param request  请求
     * @param response 响应
     * @param chain    过滤器链
     */
    @Override
    public void doFilterInternal(HttpServletRequest request,
                                 HttpServletResponse response,
                                 FilterChain chain) throws IOException, ServletException {
        // 获取请求头中的token字符串
        String tokenHeader = request.getHeader(JwtUtils.TOKEN_NAME);
        // 如果请求头中没有token信息则直接放行了，下面做认证过滤
        if (tokenHeader == null || !tokenHeader.startsWith(JwtUtils.TOKEN_PREFIX)) {
            // 放行
            chain.doFilter(request, response);
        } else {
            // 如果请求头中有token，则进行解析，并且设置认证信息
            /**
             * Spring Security的过滤器链中，位于最后的FilterSecurityInterceptor是用来进行权限认证
             * 它会从SecurityContextHolder获取Authentication
             * 所以我们只需要将token里面的用户信息和权限信息存放到SecurityContext中
             * SecurityContextHolder.getContext().setAuthentication 即可
             */

            SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader, response));
            super.doFilterInternal(request, response, chain);
        }

    }

    // 这里从token中获取用户信息并新建一个token

    /**
     * 解析前端传递的token字符串，获取用户信息存储到Authentication中
     * @param tokenHeader 前端携带的token字符串
     * @param response 响应
     * @return Authentication实现类，存储用户权限信息
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader, HttpServletResponse response) throws IOException {
        // 去token前缀，前缀替换成空串""
        String token = tokenHeader.replace(JwtUtils.TOKEN_PREFIX, "");
        // 获取响应输出流
        ServletOutputStream out = response.getOutputStream();
        UserInfo userInfo = null;
        Result errorInfoDTO = new Result();
        try {
            // 通过公钥解密token，获取用户信息
            userInfo = JwtUtils.getInfoFromToken(token, jwtProperties.getPublicKey());
            // 根据token 获取 用户信息  封装到UsernamePasswordAuthenticationToken对象中
            String username = userInfo.getUsername();
            if (username != null) {
                // 切割UserInfo中权限字符串封装到集合中
                String[] roles1 = userInfo.getRoles().split("-");
                List<String> list = Arrays.asList(roles1);
                List<GrantedAuthority> listRoles = new ArrayList<>();
                for (String s : list) {
                    listRoles.add(new SimpleGrantedAuthority(s));
                }
                // 存放
                return new UsernamePasswordAuthenticationToken(username, null, listRoles);
            }
        } catch (Exception e) {
            // 异常解析失败
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            errorInfoDTO.setFlag(false);
            errorInfoDTO.setMessage("认证解析失败");
            String s = JSONObject.toJSONString(errorInfoDTO);
            out.write(s.getBytes());
            out.flush();
            out.close();
        }
        return null;
    }
}
